<?php


namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use DateTimeInterface;

class BaseModel extends Model
{
    use Criteria;

    //use ApiSoftDeletes; laravels 下常驻内存 as Name 会出问题
    /**
     * 插入时间字段
     */
    const CREATED_AT = 'create_time';

    /**
     * 更新时间字段
     */
    const UPDATED_AT = 'update_time';

    /**
     * 状态字段
     */
    const DELETED_AT = 'status';

    /**
     * 正常状态
     */
    const STATUS_ENABLED = 0;
    /**
     * 禁用状态
     */
    const STATUS_DISABLED = 1;

    /**
     * 删除状态
     */
    const STATUS_DELETED = 2;

    /**
     * 该模型是否被自动维护时间戳
     *
     * @var bool
     */
    public $timestamps = true;
    /**
     * 别名名称
     * @var null
     */
    protected $aliasName = null;

    /**
     * 模型的日期字段保存格式。
     *
     * @var string
     */
    protected $dateFormat = 'Y-m-d H:i:s';

    protected $columns;

    /* public function __construct(array $attributes = [])
     {
         parent::__construct($attributes);
         //自动添加字段
         $this->setFillable();
     }*/

    /**
     * 获取数据库的列
     * @return mixed
     */
    public function getColumns()
    {
        if (!$this->columns) {
            $key = get_class($this);
            $cache = Cache::get($key);
            if (empty($cache)) {
                $cache = Schema::getColumnListing($this->getTable());
                Cache::put($key, $cache, config('cache.columns_expire'));
            }
            $this->columns = $cache;
        }
        return $this->columns;
    }


    /**
     * 自动获取数据库表字段
     */
    protected function setFillable()
    {
        $key = get_class($this);
        $cache = Cache::store('file')->get($key);
        if (empty($cache)) {
            $columns = Schema::getColumnListing($this->table);
            $cache = $columns;
            Cache::store('file')->put($key, $columns, config('cache.columns'));
        }
        $this->fillable = $cache;
    }

    /**
     * 过滤掉非数据库字段的数据
     * @param $data
     * @return array
     */
    public function filter($data)
    {
        if (empty($this->fillable)) {
            $this->setFillable();
        }
        $result = [];
        if (empty($data) || !is_array($data)) {
            return $result;
        }
        foreach ($this->fillable as $item) {
            if (isset($data[$item])) {
                $result[$item] = $data[$item];
            }
        }
        return $result;
    }

    /**
     * 覆盖setCreatedAt 不添加createAt字段
     *
     * @param mixed $value
     * @return $this
     */
    public function setCreatedAt($value)
    {
        if (!empty(static::CREATED_AT)) {
            $this->{static::CREATED_AT} = $value;
        }
        if (!empty(static::UPDATED_AT)) {
            $this->{static::UPDATED_AT} = $value;
        }
        return $this;
    }

    /**
     * Set the value of the "updated at" attribute.
     *
     * @param mixed $value
     * @return $this
     */
    public function setUpdatedAt($value)
    {
        if (!empty(static::UPDATED_AT)) {
            $this->{static::UPDATED_AT} = $value;
        }
        return $this;
    }

    /**
     * 别名
     * @param $name
     */
    public function alias($name)
    {
        $this->aliasName = $name;
        return $this->from($this->getTable() . ' as ' . $name);
    }

    /**
     * 覆盖父类方法 新建连接
     * @return Illuminate\Database\Query\Builder|QueryBuilder
     */
    protected function newBaseQueryBuilder()
    {
        $connection = $this->getConnection();
        return new QueryBuilder(
            $connection, $connection->getQueryGrammar(), $connection->getPostProcessor()
        );
    }


    /**
     * 覆盖父类方法
     */
    public function newEloquentBuilder($query)
    {
        return new EloquentBuilder($query);
    }

    //重载model里的事件通知
    public function fireModelEvent($event, $halt = true)
    {
        parent::fireModelEvent($event, $halt);
    }

    /**
     * 是否是json字段
     * @param $field
     * @return bool
     */
    public function isJsonCastingField($field)
    {
        if (isset($this->casts[$field]) && $this->casts[$field] == 'array') {
            return true;
        }
        return false;
    }

    /**
     * 获取别名名称
     * @return null
     */
    public function getAliasName()
    {
        return $this->aliasName;
    }

    /**
     * Set the table associated with the model.
     *
     * @param string $table
     * @return $this
     */
    public function setTable($table)
    {
        $table = trim($table);
        $this->table = $table;
        if (strpos($table, ' as ')) {
            list($table, $alias) = explode(' as ', $table);
            if (!empty($alias)) {
                $this->aliasName = trim($alias);
            }
        }
        return $this;
    }

    /*  @param DateTimeInterface $date
     *
     * @return string
     */
    protected function serializeDate(DateTimeInterface $date)
    {
        return $date->format('Y-m-d H:i:s');
    }

    /**
     * 解析时间值
     * @param mixed $value
     * @return \Illuminate\Support\Carbon|mixed
     */
    protected function asDateTime($value)
    {
        if (null == $value || '0000-00-00 00:00:00' == $value) {
            return $value;
        }

        $format = $this->getDateFormat();
        try {
            $date = Date::createFromFormat($format, $value);
        } catch (InvalidArgumentException $e) {
            $date = false;
        }

        return $date ?: Date::parse($value);
    }

    /**
     * 获取分页行数
     * @param $data
     * @return int
     */
    protected function getPageSize($data)
    {
        return $data['page_size'] ?? config('app.app_rows');
    }

    /**
     * 获取页码
     * @param $data
     * @return int
     */
    protected function getPageNo($data)
    {
        return $data['page'] ?? config('app.app_page_no');
    }

    /**
     * 获取查询偏移量
     * @param $data
     * @return int
     */
    protected function getSkip($page, $pageSize)
    {
        return ($page - 1) * $pageSize;;
    }

    /**
     * 获取分页参数
     * */
    protected function getPaginatorParams($params)
    {

        $pageSize = $this->getPageSize($params);
        $page = $this->getPageNo($params);
        $skip = $this->getSkip($page, $pageSize);
        return array($pageSize, $page, $skip);
    }

    /**
     * 构造返回页面数据
     * @param $data //数据
     * @param $formNb //开始数量
     * @param $page //页数
     * @param $perPageSize //每页数量
     * @param $total //总数量
     * @return mixed
     */
    public function buildPaginator($data = [], $formNb = 0, $page = 1, $perPageSize = 10, $total = 0)
    {
        $lastPage = ceil($total / $perPageSize);
        $toNb = $page * $perPageSize;
        if (empty($formNb)) {
            $formNb = $toNb - $perPageSize;
        }
        if ($toNb > $total) {
            $toNb = $total;
        }
        $resultDdata = array(
            'current_page' => $page,
            'data' => $data,
            'from' => $formNb,
            'last_page' => $lastPage,
            'per_page' => $perPageSize,
            'to' => $toNb,
            'total' => $total,
        );
        return $resultDdata;
    }

    /**
     * 字段唯一性性验证
     * 返回ture 代表数据库不存在,是唯一的 false 代表数据库存在,不是唯一的
     * @param $field
     * @param $value
     * @param $criteria
     * @return bool
     */
    public function checkFieldUnique($field, $criteria)
    {
        $existData = $this->newInstance()->buildQuery($criteria)->selectRaw(DB::raw($field))->exists();
        if ($existData) {
            return false;
        } else {
            return true;
        }
    }


    /**
     * @param bool $alias
     * @return \Illuminate\Database\Query\Builder
     */
    public function db($alias = false)
    {
        return DB::table($this->getTable() . ($alias ? " as $alias" : ''));
    }

    /**
     * 批量写入数据
     * @param $datas
     * @return bool
     */
    public function insertAll($datas)
    {
        if (empty($datas)) {
            return false;
        }
        foreach ($datas as &$data) {
            if ($this->timestamps) {
                $createAt = $this::CREATED_AT;
                $updateAt = $this::UPDATED_AT;
                $data[$updateAt] = $data[$createAt] = nowTime();
            }
        }
        return DB::table($this->table)->insert($datas);
    }

    /**
     * 保存一条记录或者多条记录
     * @param array $data
     * @param mixed $where
     * @return int
     */
    public function updateData($data, $where = false)
    {
//        $data = $this->filter($data);
        if ($this->timestamps) {
            $updateAt = $this::UPDATED_AT;
            $data[$updateAt] = nowTime();
        }
        if (!$where) {
            $id = $data[$this->getKeyName()];
            unset($data[$this->getKeyName()]);
            $where = [
                $this->getKeyName() => $id
            ];
        }
        return $this->bindCriteria($this->db(), $where)->update($data);
    }

    /**
     * 新增记录
     * @param $data
     * @return int
     */
    public function insertData($data)
    {
//        $data = $this->filter($data);
        if ($this->timestamps) {
            $createAt = $this::CREATED_AT;
            $updateAt = $this::UPDATED_AT;
            if ($createAt) {
                $data[$createAt] = nowTime();
            }
            if ($updateAt) {
                $data[$updateAt] = nowTime();
            }
        }
        return $this->db()->insertGetId($data);
    }

    /**
     * 列递增
     * @param array $criteria 条件
     * @param string $column 列
     * @param int $value 值
     * @return int
     */
    public function incrBy($criteria, $column, $value = 1)
    {
        $db = $this->db();
        return $this->bindCriteria($db, $criteria)->increment($column, $value);
    }

    /**
     * 列递减
     * @param array $criteria 条件
     * @param string $column 列
     * @param int $value 值
     * @return int
     */
    public function decrBy($criteria, $column, $value = 1)
    {
        $db = $this->db();
        return $this->bindCriteria($db, $criteria)->decrement($column, $value);
    }

    /**
     * 获取一条记录
     * @param $id
     * @param $field
     * @return array
     */
    public function findOneById($id, $field = "*")
    {
        $db = $this->db()->selectRaw($field);
        if (method_exists($this, 'runSoftDelete') && !isset($criteria['status'])) {
            $db = $db->whereIn('status', [0, 1]);
        }
        $data = $db->where([$this->getKeyName() => $id])->first();
        return (array)$data;
    }

    /**
     * 根据条件获取一条记录
     * @param $criteria
     * @param $field
     * @return array
     */
    public function findOneBy($criteria, $field = "*")
    {
        $db = $this->db()->selectRaw($field);
        if (method_exists($this, 'runSoftDelete') && !isset($criteria['status'])) {
            $db = $db->whereIn('status', [0, 1]);
        }
        $data = $this->bindCriteria($db, $criteria)->first();
        return (array)$data;
    }

    /**
     * 根据条件获取一条记录
     * @param $criteria
     * @param $field
     * @return int
     */
    public function countBy($criteria)
    {
        $db = $this->db();
        if (method_exists($this, 'runSoftDelete') && !isset($criteria['status'])) {
            $db = $db->whereIn('status', [0, 1]);
        }
        return $this->bindCriteria($db, $criteria)->count();
    }

    /**
     * 绑定查询条件
     * @param \Illuminate\Database\Query\Builder $db
     * @param $criteria
     * @return \Illuminate\Database\Query\Builder
     */
    public function bindCriteria($db, &$criteria)
    {
        $where = [];
        foreach ($criteria as $k => $v) {
            if (!is_numeric($k)) {
                if (is_array($v)) {
                    if ($v[0] == 'in') {
                        $db = $db->whereIn($k, $v[1]);
                    } else if ($v[0] == 'not_in') {
                        $db = $db->whereNotIn($k, $v[1]);
                    } else {
                        $where[] = [$k, $v[0], $v[1]];
                    }
                } else {
                    $where[$k] = $v;
                }
            } else if (is_array($v)) {
                if ($v[1] == 'in') {
                    $db = $db->whereIn($v[0], $v[2]);
                } else if ($v[1] == 'not_in') {
                    $db = $db->whereNotIn($v[0], $v[2]);
                } else {
                    $where[] = $v;
                }
            }
        }
        return $db->where($where);
    }

    /**
     * 根据条件获取多条记录
     * @param $criteria
     * @param $field
     * @return array
     */
    public function findBy($criteria, $field = "*")
    {
        $db = $this->db()->selectRaw($field);
        if (method_exists($this, 'runSoftDelete') && !isset($criteria['status'])) {
            $db = $db->whereIn('status', [0, 1]);
        }
        return $this->bindCriteria($db, $criteria)
            ->get()->toArray();
    }

    /**
     * 根据条件删除
     * @param $criteria
     * @return int
     */
    public function deleteBy($criteria)
    {
        $db = $this->db();
        if (method_exists($this, 'runSoftDelete') && !isset($criteria['status'])) {
            return $this->bindCriteria($db, $criteria)->update(['status' => 2]);
        } else {
            return $this->bindCriteria($db, $criteria)->delete();
        }
    }

    /**
     * 分页查询
     * @param array $where
     * @param string $field
     * @param string $orderBy 默认为按id desc
     * @param int $pageSize 0表示使用env文件中配置的默认每页条数
     * @return array
     */
    public function pageQuery($where, $field = '*', $orderBy = 'id desc', $pageSize = 0)
    {
        if (!$pageSize) {
            $pageSize = intval(config('app.app_rows'));
        } else {
            $pageSize = intval($pageSize);
        }
        $db = $this->db();
        return $this->bindCriteria($db, $where)->selectRaw($field)
            ->orderByRaw($orderBy)->paginate($pageSize)->toArray();
    }

    /**
     * 返回正常状态的值
     * @return int
     */
    public function getStatusEnabled()
    {
        return self::STATUS_ENABLED;
    }

    /**
     * 返回禁用状态的值
     * @return int
     */
    public function getStatusDisabled()
    {
        return self::STATUS_DISABLED;
    }

}
